﻿#include "orm.h"
#include "qconnectionpool.h"

namespace flow
{

// QVector<double>{1,2,3,4} --->  "1,2,3,4";
QString join(QVector<QString> vec, const QString& delimiter)
{
    QString result;
    if (vec.empty()) {
        return "";
    }
    for (auto it = vec.begin(); it != vec.end() - 1; ++it) {
        result += (*it) + delimiter;
    }
    return result + (*vec.rbegin());
}

dao_t::dao_t()
{
    // qDebug() << "eprorm ### openConnection";
    _db        = QDbConnectionPool::openConnection();
    _sql_query = QSqlQuery(_db);
}

dao_t::dao_t(const QSqlDatabase& db, const QString& table)
    : _table(table)
    , _db(db)
    , _sql_query(QSqlQuery(_db))
{ }

dao_t::dao_t(const QString& table)
    : dao_t()
{
    set_table(table);
}

dao_t& dao_t::operator=(dao_t&& rhs) noexcept
{
    if (this != &rhs) {
        this->_query_type      = rhs._query_type;
        this->_limit           = rhs._limit;
        this->_size            = rhs._size;
        this->_conditions      = rhs._conditions;
        this->_table           = rhs._table;
        this->_results         = rhs._results;
        this->_dirty_fields    = rhs._dirty_fields;
        this->_order_by        = rhs._order_by;
        this->_group_by        = rhs._group_by;
        this->_listRecord      = rhs._listRecord;
        this->_db              = rhs._db;
        this->_sql_query       = std::move(rhs._sql_query);
        this->_last_error_text = rhs._last_error_text;
    }
    return *this;
}

dao_t& dao_t::operator=(const dao_t& rhs) noexcept
{
    *this = std::move(const_cast<dao_t&>(rhs));
    return *this;
}

dao_t::~dao_t()
{
    // qDebug() << "eprorm ### dao deleted";
}

dao_t dao_t::from(const QString& table)
{
    return dao_t(table);
}

const QString& dao_t::last_error()
{
    return _last_error_text;
}

QSqlQuery& dao_t::get_query()
{
    return _sql_query;
}

QSqlDatabase dao_t::get_db()
{
    return _db;
}

bool dao_t::transaction()
{
    return _db.transaction();
}

bool dao_t::commit()
{
    return _db.commit();
}

bool dao_t::rollback()
{
    return _db.rollback();
}

void dao_t::set_on_log(std::function<void(const QString&)> cb)
{
    _on_log = cb;
}

void dao_t::set_on_before_query(std::function<void(const QString&)> cb)
{
    _on_before_query = cb;
}

dao_t& dao_t::where(const QString& query)
{
    add_condition(query);
    return *this;
}

dao_t& dao_t::where(const QString& col, const QString& value)
{
    add_condition(col, "=", value);
    return *this;
}

dao_t& dao_t::where(const QString& col, const QString& op, const QString& value)
{
    add_condition(col, op, value);
    return *this;
}

dao_t& dao_t::where_equal(const QString& col, const QString& value)
{
    add_condition(col, "=", value);
    return *this;
}

dao_t& dao_t::where_not_equal(const QString& col, const QString& value)
{
    add_condition(col, "!=", value);
    return *this;
}

dao_t& dao_t::where_like(const QString& col, const QString& value)
{
    add_condition(col, "LIKE", value);
    return *this;
}

dao_t& dao_t::where_not_like(const QString& col, const QString& value)
{
    add_condition(col, "NOT LIKE", value);
    return *this;
}

dao_t& dao_t::select(const QString& col)
{
    add_result_column(col);
    return *this;
}

dao_t& dao_t::select(const QVector<QString>& cols)
{
    for (const auto& col : cols) {
        add_result_column(col);
    }
    return *this;
}

dao_t& dao_t::limit(int limit)
{
    set_limit(limit);
    return *this;
}

dao_t& dao_t::order_by_expr(const QString& expr)
{
    add_order_by(expr, "");
    return *this;
}

dao_t& dao_t::order_by_asc(const QString& col)
{
    add_order_by(col, "ASC");
    return *this;
}

dao_t& dao_t::order_by_desc(const QString& col)
{
    add_order_by(col, "DESC");
    return *this;
}

const dao_t& dao_t::get(int limit)
{
    set_limit(limit);
    find_values();
    return *this;
}

const dao_t& dao_t::get(int from, int size)
{
    set_size(size);
    return get(from);
}

const dao_t& dao_t::find_many(int limit)
{
    return get(limit);
}

const dao_t& dao_t::find_many(int from, int size)
{
    return get(from, size);
}

const QSqlRecord& dao_t::const_record(int index) const
{
    return _listRecord.at(index);
}

QSqlRecord& dao_t::record(int index)
{
    // if (index < _listRecord.size()) {
    // }
    // if (empty()) {
    //	find_many();
    // }
    return _listRecord[index];
}

const QVector<QSqlRecord>& dao_t::records()
{
    return _listRecord;
}

uint32_t dao_t::count(const QString& col)
{
    _results.clear();
    add_result_column("COUNT(" + col + ")");
    find_values();
    return record().value(0).toUInt();
}

QVariant dao_t::minimum(const QString& col)
{
    _results.clear();
    add_result_column("MIN(" + col + ")");
    find_values();
    return record().value(0);
}

QVariant dao_t::maximum(const QString& col)
{
    _results.clear();
    add_result_column("MAX(" + col + ")");
    find_values();
    return record().value(0);
}

QVariant dao_t::avg(const QString& col)
{
    _results.clear();
    add_result_column("AVG(" + col + ")");
    find_values();
    return record().value(0);
}

QVariant dao_t::sum(const QString& col)
{
    _results.clear();
    add_result_column("SUM(" + col + ")");
    find_values();
    return record().value(0);
}

bool dao_t::remove()
{
    return remove_records();
}

dao_t& dao_t::create_insert()
{
    _listRecord.clear();
    _listRecord.resize(1);
    set_query_type(QueryType::Insert);
    return *this;
}

dao_t& dao_t::create_update()
{
    _listRecord.clear();
    _listRecord.resize(1);
    set_query_type(QueryType::Update);
    return *this;
}

int dao_t::insert(QVariantMap fields)
{
    set_query_type(QueryType::Insert);
    set_record_fields(fields);
    return insert_record();
}

int dao_t::update(QVariantMap fields)
{
    set_query_type(QueryType::Update);
    set_record_fields(fields);
    return update_records();
}

int dao_t::save()
{
    int ret;
    if (_query_type == QueryType::Insert) {
        ret = insert_record();
    }
    else {
        ret = update_records();
    }
    clean_dirty_fields();
    return ret;
}

dao_t& dao_t::set(const QString& col, const QVariant& value, int row)
{
    dirt_field(col);
    auto& record = this->record();
    if (record.contains(col)) {
        record.setValue(col, value);
    }
    else {
        record.append(QSqlField(col));
        record.setValue(col, value);
    }
    // qDebug() << record.value(key);
    return *this;
}

int dao_t::size() const
{
    return _listRecord.size();
}

bool dao_t::empty() const
{
    return size() == 0;
}

QVariant dao_t::operator[](const QString& key) const
{
    return value(key);
}

QVariant dao_t::value(int index, int row) const
{
    return const_record().value(index);
}

QVariant dao_t::value(const QString& field, int row) const
{
    return const_record(row).value(field);
}

dao_t& dao_t::refresh()
{
    QString sql = _sql_query.executedQuery();
    query(sql);
    return *this;
}

QString dao_t::build_result_columns() const
{
    if (_results.empty()) {
        return "*";
    }
    else {
        return join(_results, ",");
    }
}

QString dao_t::build_conditions() const
{
    const QString conditions = join(_conditions, " AND ");
    if (conditions.isEmpty()) {
        return "";
    }
    return "WHERE " + conditions;
}

QString dao_t::build_field_keys()
{
    QString keys;
    const auto record = this->record();
    for (int i = 0; i < record.count(); i++) {
        keys += "'" + record.fieldName(i) + "',";
    }
    keys.chop(1);
    return keys;
}

QString dao_t::build_field_values()
{
    QString values;
    auto record = this->record();
    for (int i = 0; i < record.count(); i++) {
        auto d = record.value(i);
        values += "\'" + d.toString() + "\'";
        if (i != record.count() - 1) {
            values += ",";
        }
    }
    return values;
}

QString dao_t::build_changes()
{
    QString changes;
    if (_dirty_fields.empty()) {
        return "";
    }
    for (auto field_name : _dirty_fields) {
        changes += field_name + "=\'" + value(field_name).toString() + "\',";
    }
    // 删除最右边 1 个字符
    changes.chop(1);
    return changes;
}

QString dao_t::build_order_by()
{
    if (_order_by.isEmpty()) {
        return "";
    }
    QString orders = "ORDER BY ";
    orders += join(_order_by, ",");
    return orders;
}

QString dao_t::build_group_by()
{
    QString groups = "GROUP BY ";
    groups += join(_group_by, ",");
    return groups;
}

QString dao_t::build_from()
{
    QString query = "FROM ";
    query += _table;
    return query;
}

QString dao_t::build_into()
{
    QString query = "INTO ";
    query += _table;
    return query;
}

QString dao_t::build_limit()
{
    if (_limit == 0) {
        return "";
    }
    QString query = "LIMIT " + QString::number(_limit);
    if (_size > 0) {
        query += "," + QString::number(_size);
    }
    return query;
}

QString dao_t::build_select()
{
    QString query = "SELECT ";
    query += build_result_columns() + " " + build_from() + " " + build_conditions() + " " + build_order_by() + " " + build_limit();
    return query;
}

QString dao_t::build_update()
{
    QString query = "UPDATE ";
    query += _table + " SET " + build_changes() + " " + build_conditions();
    return query;
}

QString dao_t::build_delete()
{
    QString query = "DELETE ";
    query += build_from() + " " + build_conditions();
    return query;
}

QString dao_t::build_insert()
{
    // QString query = "INSERT OR REPLACE ";
    //  replace into
    QString query = "INSERT ";
    query += build_into() + " (" + build_field_keys() + ") VALUES (" + build_field_values() + ")";
    return query;
}

QString dao_t::build()
{
    switch (_query_type) {
    case QueryType::Select: return build_select();
    case QueryType::Update: return build_update();
    case QueryType::Delete: return build_delete();
    case QueryType::Insert: return build_insert();
    default:;
    }
    return QString();
}

void dao_t::set_with_no_dirt(const QString& key, const QString& value)
{
    record().setValue(key, value);
}

void dao_t::set_query_type(QueryType type)
{
    _query_type = type;
}

void dao_t::set_table(const QString& table)
{
    _table = table;
}

void dao_t::set_limit(int limit)
{
    _limit = limit;
}

void dao_t::add_result_column(const QString& col)
{
    _results.push_back(col);
}

void dao_t::add_condition(const QString& col, const QString& op, const QString& value)
{
    _conditions.push_back("`" + col + "` " + op + " \'" + value + "\'");
}

void dao_t::add_condition(const QString& query)
{
    _conditions.push_back(query);
}

void dao_t::add_order_by(const QString& col, const QString& order)
{
    _order_by.push_back(col + " " + order);
}

void dao_t::add_group_by(const QString& col)
{
    _group_by.push_back(col);
}

bool dao_t::query(const QString& sql)
{
    try {
        // qDebug() << "eprorm ### " << sql;
        if (_on_before_query) {
            _on_before_query(sql);
        }
        const bool is_ok = _sql_query.exec(sql);
        if (is_ok) {
            while (_sql_query.next()) {
                _listRecord.push_back(_sql_query.record());
            }
        }
        // const auto resultSize = qMax(_limit, _size);
        // if (_listRecord.size() < resultSize) {
        //	//if (_listRecord.size() != _limit && _limit > 0) {
        //	_listRecord.resize(resultSize);
        // }
        set_error(_sql_query, &_last_error_text);
        return is_ok;
    }
    catch (...) {
    }
    _last_error_text = "sql " + sql;
    return false;
}

void dao_t::set_error(const QSqlQuery& err_query, QString* err_text)
{
    const QString err = err_query.lastError().text();
    if (err.trimmed().isEmpty()) {
        return;
    }
    qDebug() << err;
    if (err_text) {
        *err_text = err;
    }
}

void dao_t::dirt_field(const QString& field_name)
{
    _dirty_fields.push_back(field_name);
}

void dao_t::clean_dirty_fields()
{
    _dirty_fields.clear();
}

bool dao_t::find_values()
{
    const QString sql = build_select();
    return query(sql);
}

int dao_t::update_records()
{
    transaction();
    const QString sql = build_update();
    if (query(sql)) {
        commit();
    }
    else {
        rollback();
    }
    return _sql_query.numRowsAffected();
}

int dao_t::insert_record()
{
    const QString sql = build_insert();
    transaction();
    if (!query(sql)) {
        rollback();
        return -1;
    }
    commit();
    return _sql_query.lastInsertId().toUInt();
}

int dao_t::remove_records()
{
    const QString sql = build_delete();
    query(sql);
    return _sql_query.numRowsAffected();
}

void dao_t::set_record_fields(QVariantMap fields)
{
    if (_listRecord.empty()) {
        _listRecord.resize(1);
    }
    for (auto& key : fields.keys()) {
        set(key, fields[key]);
    }
}

int dao_t::exec_raw(const QString& sql, QString* last_error /*= nullptr*/)
{
    dao_t mapper;
    auto& query = mapper.get_query();
    if (query.exec(sql)) {
        return query.numRowsAffected();
    }
    set_error(query);
    return 0;
}

} // namespace epr
